<template>
  <div>
    <component :is="tag" ref="point" v-bind="tagAttrs" :class="classes" :style="styles">
      <slot></slot>
    </component>
    <div v-show="slot" :style="slotStyle"></div>
  </div>
</template>
<script>
import { on, off } from 'element-ui/src/utils/dom';
const prefixCls = 'el-affix';

function getScroll(target, top) {
  const prop = top ? 'pageYOffset' : 'pageXOffset';
  const method = top ? 'scrollTop' : 'scrollLeft';

  let ret = target[prop];

  if (typeof ret !== 'number') {
    ret = target[method];
  }

  return ret;
}

function getOffset(element, target) {
  const rect = element.getBoundingClientRect();

  const scrollTop = getScroll(target, true);
  const scrollLeft = getScroll(target);

  const docEl = target.document ? target.document.body : target;
  const clientTop = docEl.clientTop || 0;
  const clientLeft = docEl.clientLeft || 0;

  return {
    top: rect.top + scrollTop - clientTop,
    left: rect.left + scrollLeft - clientLeft
  };
}

export default {
  name: 'ElAffix',
  props: {
    offsetTop: {
      type: Number,
      default: 0
    },
    offsetBottom: {
      type: Number
    },
    useCapture: {
      type: Boolean,
      default: false
    },
    target: {},
    tag: {
      type: String,
      default: 'div'
    },
    tagAttrs: {}
  },
  data() {
    return {
      affix: false,
      styles: {},
      slot: false,
      slotStyle: {}
    };
  },
  computed: {
    offsetType() {
      let type = 'top';
      if (this.offsetBottom >= 0) {
        type = 'bottom';
      }

      return type;
    },
    classes() {
      return [
        {
          [`${prefixCls}`]: this.affix
        }
      ];
    }
  },
  watch: {
    target(target, oldTarget) {
      if (target) {
        on(target, 'scroll', this.handleScroll, this.useCapture);
        on(target, 'resize', this.handleScroll, this.useCapture);
        this.$nextTick(() => {
          this.handleScroll();
        });
      }
      if (oldTarget) {
        off(oldTarget, 'scroll', this.handleScroll, this.useCapture);
        off(oldTarget, 'resize', this.handleScroll, this.useCapture);
      }
    }
  },
  mounted() {
    const target = this.target
    if (target) {
      on(target, 'scroll', this.handleScroll, this.useCapture);
      on(target, 'resize', this.handleScroll, this.useCapture);
      this.$nextTick(() => {
        this.handleScroll();
      });
    }
  },
  beforeDestroy() {
    const target = this.target
    if (target) {
      off(target, 'scroll', this.handleScroll, this.useCapture);
      off(target, 'resize', this.handleScroll, this.useCapture);
    }
  },
  methods: {
    update() {
      if (this.affix) {
        const target = this.target || window
        const elOffset = getOffset(this.$el, target);
        this.slotStyle = {
          width: this.$refs.point.clientWidth + 'px',
          height: this.$refs.point.clientHeight + 'px'
        };
        this.styles = {
          ...this.styles,
          left: `${elOffset.left}px`,
          width: `${this.$el.offsetWidth}px`
        };
      }
    },
    handleScroll() {
      const target = this.target || window
      const affix = this.affix;
      const scrollTop = getScroll(target, true);
      const elOffset = getOffset(this.$el, target);
      const windowHeight = window.innerHeight;
      const elHeight = this.$el.getElementsByTagName('div')[0].offsetHeight;

      // Fixed Top
      if ((elOffset.top - this.offsetTop) < scrollTop && this.offsetType === 'top' && !affix) {
        this.affix = true;
        this.slotStyle = {
          width: this.$refs.point.clientWidth + 'px',
          height: this.$refs.point.clientHeight + 'px'
        };
        this.slot = true;
        this.styles = {
          top: `${this.offsetTop}px`,
          left: `${elOffset.left}px`,
          width: `${this.$el.offsetWidth}px`
        };

        this.$emit('change', true);
      } else if ((elOffset.top - this.offsetTop) > scrollTop && this.offsetType === 'top' && affix) {
        this.slot = false;
        this.slotStyle = {};
        this.affix = false;
        this.styles = null;

        this.$emit('change', false);
      }

      // Fixed Bottom
      if ((elOffset.top + this.offsetBottom + elHeight) > (scrollTop + windowHeight) && this.offsetType === 'bottom' && !affix) {
        this.affix = true;
        this.styles = {
          bottom: `${this.offsetBottom}px`,
          left: `${elOffset.left}px`,
          width: `${this.$el.offsetWidth}px`
        };

        this.$emit('change', true);
      } else if ((elOffset.top + this.offsetBottom + elHeight) < (scrollTop + windowHeight) && this.offsetType === 'bottom' && affix) {
        this.affix = false;
        this.styles = null;

        this.$emit('change', false);
      }
    }
  }
};
</script>
